﻿/**
 * Peter
 * 
 * Created by: Peter Development Team
 *    http://peter.codeplex.com/
 * 
 * GNU General Public License version 2 (GPLv2)
 *    http://peter.codeplex.com/license
 *    
 *  This code is provided on an AS IS basis, with no WARRANTIES,
 *  CONDITIONS or GUARANTEES of any kind.
 *  
 **/

using System;
using System.Collections.Generic;

namespace Peter.Common.Services
{
   /// <summary>
   /// Maintains application services and contracts.
   /// </summary>
   public static class ServiceManager
   {
      private static readonly Dictionary <Type, Service> s_Services;

      /// <summary>
      /// Static constructor.
      /// </summary>
      static ServiceManager ()
      {
         s_Services = new Dictionary<Type, Service> ();
      }

      /// <summary>
      /// Number of services available.
      /// </summary>
      public static int Count { get { return s_Services.Count; } }

      /// <summary>
      /// Registers a service.
      /// </summary>
      /// <typeparam name="TContract">Type of contract of requested service.</typeparam>
      ///<typeparam name="TService">The implentation of the type contract.</typeparam>
      public static void Add<TContract, TService> () where TService : TContract
      {
         Add<TContract, TService> (false, null);
      }

      /// <summary>
      /// Registers a service type as a singleton.
      /// </summary>
      /// <typeparam name="TContract">Type of contract of requested service.</typeparam>
      ///<typeparam name="TService">The implentation of the type contract.</typeparam>
      public static void AddSingleton<TContract, TService> () where TService : TContract
      {
         Add<TContract, TService> (true, null);
      }

      /// <summary>
      /// Registers a service as a singleton, passing the service object to use.
      /// </summary>
      /// <typeparam name="TContract">Type of contract of requested service.</typeparam>
      ///<typeparam name="TService">The implentation of the type contract.</typeparam>
      /// <exception cref="ArgumentNullException">If service is of null.</exception>
      public static void AddSingleton<TContract, TService> (TService service) where TService : class, TContract
      {
         Add<TContract, TService> (true, service);
      }

      /// <summary>
      /// Registers a service.
      /// </summary>
      /// <typeparam name="TContract">Type of contract of requested service.</typeparam>
      ///<typeparam name="TService">The implentation of the type contract.</typeparam>
      /// <param name="isSingleton">true if service is Singleton; otherwise false.</param>
      /// <param name="service">A default implementation or null.</param>
      private static void Add<TContract, TService> (bool isSingleton, object service) where TService : TContract
      {
         s_Services.Add (typeof (TContract), new Service (typeof (TService), isSingleton, service));
      }

      ///<summary>
      /// Adds a precreated service to the service type queue.
      ///</summary>
      ///<param name="service">Precreated service.</param>
      /// <typeparam name="TContract">Type of contract of requested service.</typeparam>
      ///<typeparam name="TService">The implentation of the type contract.</typeparam>
      public static void Enqueue<TContract, TService> (TService service) where TService : TContract
      {
         var serviceInformation = s_Services[typeof (TContract)];
         if (serviceInformation.IsSingleton)
            throw new InvalidOperationException ("Enqueue can not be called for singleton type service interface!");
         serviceInformation.ServiceImplementations.Enqueue (service);
      }

      /// <summary>
      /// Gets a service with the given contract.
      /// </summary>
      /// <typeparam name="TContract">Type of contract of requested service.</typeparam>
      /// <returns>The requested services contract.</returns>
      public static TContract Get<TContract> ()
      {
         if (s_Services.ContainsKey(typeof (TContract)))
            return (TContract) s_Services [typeof (TContract)].Implementation;
         return default(TContract);
      }

      /// <summary>
      /// Removes all services.
      /// </summary>
      public static void Clear ()
      {
         s_Services.Clear ();
      }

      /// <summary>
      /// A service that is registered with the service manager.
      /// </summary>
      private class Service
      {
         private readonly Type m_ServiceType;
         private object m_ServiceImplementation;

         /// <summary>
         /// Indicates if the service is a singleton.
         /// </summary>
         public bool IsSingleton { get; private set;}

         /// <summary>
         /// Queue list of objects precreated to support the service.
         /// </summary>
         public Queue<object> ServiceImplementations { get; private set; }

         /// <summary>
         /// Initializes a new instance of the <see cref="Service"/> class.
         /// </summary>
         /// <param name="serviceType">Type of the service.</param>
         /// <param name="isSingleton">Whether the service is a Singleton.</param>
         /// <param name="serviceImplementation">A default implementation or null.</param>
         public Service (Type serviceType, bool isSingleton, object serviceImplementation)
         {
            if (!isSingleton)
               this.ServiceImplementations = new Queue <object> ();
            this.m_ServiceType = serviceType;
            this.IsSingleton = isSingleton;
            this.m_ServiceImplementation = serviceImplementation;
         }

         /// <summary>
         /// Gets the service implementation.
         /// </summary>
         public object Implementation
         {
            get
            {
               if (this.IsSingleton)
                  return this.m_ServiceImplementation ?? (this.m_ServiceImplementation = Activator.CreateInstance (this.m_ServiceType));
               if (this.ServiceImplementations.Count > 0)
                  return this.ServiceImplementations.Dequeue ();
               return Activator.CreateInstance (this.m_ServiceType);
            }
         }
      }
   }
}
